package async

import (
	"context"
	"gitee.com/DonHz/basic-go/webook/internal/domain"
	"gitee.com/DonHz/basic-go/webook/internal/repository"
	"gitee.com/DonHz/basic-go/webook/internal/service/sms"
	"gitee.com/DonHz/basic-go/webook/pkg/logger"
	"time"
)

type Service struct {
	svc sms.Service
	// 转异步，存储数据
	repo repository.AsyncSmsRepository
	l logger.Logger
}

func NewService(svc sms.Service, repo repository.AsyncSmsRepository, l logger.Logger) *Service {
	res := &Service{
		svc: svc,
		repo: repo,
		l: l,
	}
	go func() {
		res.StartAsyncCycle()
	}()
	return res
}

// 异步发送消息，没设计退出机制，因为程序停止的时候，它也停止了
// 抢占式调度
func (s *Service) StartAsyncCycle() {
	for {
		s.AsyncSend()
	}
}

func (s *Service) AsyncSend() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	// 抢占一个异步发送的消息，确保多个实例Pods时，只有一个实例能拿到请求
	as, err:=s.repo.PreemptWaitingSMS(ctx)
	cancel()
	switch err {
	// 抢到的
	case nil:
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		err = s.svc.Send(ctx, as.TplId, as.Args, as.Numbers...)
		if err!=nil{
			s.l.Error("执行异步发送短信失败", logger.Error(err), logger.Field{"id", as.Id})
		}
		res:=err==nil
		// 通知repository执行结果
		err = s.repo.ReportScheduleResult(ctx, as.Id, res)
		if err!=nil{
			s.l.Error("执行异步发送短信成功，但是标记到数据库失败",
				logger.Error(err),
				logger.Field{"res", res},
				logger.Field{"id", as.Id})
		}
	case repository.ErrWaitingSMSNotFound:
		time.Sleep(time.Second)
	default:
		s.l.Error("抢占异步发送短信任务失败", logger.Error(err))
		time.Sleep(time.Second)
	}
}

func (s *Service) Send(ctx context.Context, tplId string, args []string, numbers ...string) error {
	if s.needAsync() {
		err:=s.repo.Add(ctx, domain.AsyncSms{
			TplId: tplId,
			Args: args,
			Numbers: numbers,
			RetryMax: 3,
		})
		return err
	}
	return s.svc.Send(ctx, tplId, args, numbers...)
}

// 判定是否触发异步发送
func (s *Service) needAsync() bool {
	// 1. 基于平均响应时间（绝对阈值、变化趋势）
	// 2. 基于错误率，一段时间内err的请求比率大于x%
	// 退出异步
	// 1. 进入异步1分钟后
	// 2. 保留1%的流量（或者更少），继续同步发送，判定响应时间/错误率
	return true
}